// 版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package runtime

import (
	"runtime/internal/atomic"
	"unsafe"
)

const (
	_EACCES = 13
	_EINVAL = 22
)

// 不要拆分堆栈，因为调用此方法时可能没有有效的G，这会阻止我们分配更多堆栈。默认情况下，Linux的“透明巨大页面”支持将把页面合并成一个巨大的页面，如果只有一个页面的话，就会出现一个普通页面，取消下面madvise（未使用的）
func sysAlloc(n uintptr, sysStat *sysMemStat) unsafe.Pointer {
	p, err := mmap(nil, n, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_PRIVATE, -1, 0)
	if err != 0 {
		if err == _EACCES {
			print("runtime: mmap: access denied\n")
			exit(2)
		}
		if err == _EAGAIN {
			print("runtime: mmap: too much locked memory (check 'ulimit -l').\n")
			exit(2)
		}
		return nil
	}
	sysStat.add(int64(n))
	return p
}

var adviseUnused = uint32(_MADV_FREE)

func sysUnused(v unsafe.Pointer, n uintptr) {
	// 的效果。在amd64上，这意味着khugepaged可以将单个
	// 4KB页面转换为2MB，使进程的RSS膨胀了多达
	// 512X。（请参阅问题#8832和Linux内核错误
	// https:
	// 
	// 为了解决这个问题，我们在释放堆的页面时显式禁用透明的巨大
	// 页面。但是，我们必须小心地执行此操作，因为更改此标志会导致
	// spl。）它将包含v in的VMA（内存映射）转换为三个
	// VMA，以便跟踪不同区域中
	// MADV_NOHUGEPAGE标志的不同值。每个地址空间有一个
	// 65530 VMA的默认限制（sysctl 
	// vm.max_map_count），因此，我们必须小心不要创建太多的VMA（参见第12233期）.
	// 
	// 由于巨大的页面是巨大的，因此在细粒度上调整
	// MADV_NOHUGEPAGE标志没有什么用处，因此我们避免了
	// 只需在大粒度上调整
	// MADV_NOHUGEPAGE标志，就可以避免vma数量激增。这仍然是
	// 获取巨大页面的大部分好处，同时保持对VMA数量的控制。使用hugePageSize=2MB，即使
	// 一个pessimal堆在耗尽VMA之前也可以达到128GB。
	if physHugePageSize != 0 {
		// 如果这是一个大的分配，我们希望启用巨大的
		// 页面。因此，我们只需调整在包含v和v+n-1的巨大页面上使用大页面
		// 标志，并且
		// 仅当它们未对齐时才使用。
		var head, tail uintptr
		if uintptr(v)&(physHugePageSize-1) != 0 {
			// 计算包含v的巨大页面
			head = alignDown(uintptr(v), physHugePageSize)
		}
		if (uintptr(v)+n)&(physHugePageSize-1) != 0 {
			// 计算包含v+n-1的巨大页面
			tail = alignDown(uintptr(v)+n-1, physHugePageSize)
		}

		// 注意，如果标志是
		// alre，madvise将返回EINVALady set，这很有可能。我们忽略
		// 错误。
		if head != 0 && head+physHugePageSize == tail {
			// 头部和尾部不同但相邻，
			// 所以在一次调用中执行此操作。
			madvise(unsafe.Pointer(head), 2*physHugePageSize, _MADV_NOHUGEPAGE)
		} else {
			// 建议包含v和v+n-1的巨大页面。
			if head != 0 {
				madvise(unsafe.Pointer(head), physHugePageSize, _MADV_NOHUGEPAGE)
			}
			if tail != 0 && tail != head {
				madvise(unsafe.Pointer(tail), physHugePageSize, _MADV_NOHUGEPAGE)
			}
		}
	}

	if uintptr(v)&(physPageSize-1) != 0 || n&(physPageSize-1) != 0 {
		// madvise将将此舍入到任何物理页面
		// /*是范围，因此未对齐的madvise 
		// 将释放比预期更多的内存。在Linux 4.5中添加了MADV_FREE。如果不支持MADV_DONTNEED，请返回到MADV_DONTNEED。
		throw("unaligned sysUnused")
	}

	var advise uint32
	if debug.madvdontneed != 0 {
		advise = _MADV_DONTNEED
	} else {
		advise = atomic.Load(&adviseUnused)
	}
	if errno := madvise(v, n, int32(advise)); advise == _MADV_FREE && errno != 0 {
		// 不支持。
		atomic.Store(&adviseUnused, _MADV_DONTNEED)
		madvise(v, n, _MADV_DONTNEED)
	}
}

func sysUsed(v unsafe.Pointer, n uintptr) {
	// 部分撤消sysUnused 
	// 中的NOHUGEPAGE标记，用于v和v+n。这可能会在端点v和v+n留下大量的页面。尽管分配可能会覆盖这些巨大的页面。我们可以检测到这一点，并撤销端点上的所有页面。但是这可能不值得花费成本，因为当相邻分配为
	// 释放的sysUnused将再次设置NOHUGEPAGE。
	sysHugePage(v, n)
}

func sysHugePage(v unsafe.Pointer, n uintptr) {
	if physHugePageSize != 0 {
		// 将v向上取整为一个巨大的页面边界。
		beg := alignUp(uintptr(v), physHugePageSize)
		// 将v+n向下取整为一个巨大的页面边界。
		end := alignDown(uintptr(v)+n, physHugePageSize)

		if beg < end {
			madvise(unsafe.Pointer(beg), end-beg, _MADV_HUGEPAGE)
		}
	}
}

// 不要拆分堆栈，因为在没有有效的G的情况下可能调用此函数，
// 哪个在前面阻止我们分配更多堆栈。
// go:nosplit 
func sysFree(v unsafe.Pointer, n uintptr, sysStat *sysMemStat) {
	sysStat.add(-int64(n))
	munmap(v, n)
}

func sysFault(v unsafe.Pointer, n uintptr) {
	mmap(v, n, _PROT_NONE, _MAP_ANON|_MAP_PRIVATE|_MAP_FIXED, -1, 0)
}

func sysReserve(v unsafe.Pointer, n uintptr) unsafe.Pointer {
	p, err := mmap(v, n, _PROT_NONE, _MAP_ANON|_MAP_PRIVATE, -1, 0)
	if err != 0 {
		return nil
	}
	return p
}

func sysMap(v unsafe.Pointer, n uintptr, sysStat *sysMemStat) {
	sysStat.add(int64(n))

	p, err := mmap(v, n, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_FIXED|_MAP_PRIVATE, -1, 0)
	if err == _ENOMEM {
		throw("runtime: out of memory")
	}
	if p != v || err != 0 {
		throw("runtime: cannot map pages in arena address space")
	}
}
